#pragma warning(disable:4244)
#pragma warning(disable:4711)

#define _CRT_RAND_S
#undef _CRTIMP
#define _CRTIMP//SK for some reason in as DLL is defined - it starts to import funcions with 
//_CRTIMP so it does not find rand _ddlimport ( and __imp_ before functions - but this is not applicable to statically linked standard libs).
#include <streams.h>
#include <stdio.h>
#include <olectl.h>
#include <dvdmedia.h>
#include "filters.h"

#include <stdlib.h>

#include "ximage.h"

#include <algorithm>

#include <Psapi.h>

#include "Ini.h"

using namespace std;
//////////////////////////////////////////////////////////////////////////
//  CVCamera is the source filter which masquerades as a capture device
//////////////////////////////////////////////////////////////////////////


 

CUnknown * WINAPI CVCamera::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{
    ASSERT(phr);
    CUnknown *punk = new CVCamera(lpunk, phr);
    return punk;
}

CVCamera::CVCamera(LPUNKNOWN lpunk, HRESULT *phr) : 
    CSource(NAME("Virtual Flash Cam"), lpunk, CLSID_VirtualFlashCam)
{
    ASSERT(phr);
    CAutoLock cAutoLock(&m_cStateLock);
    // Create the one and only output pin
    m_paStreams = (CSourceStream **) new CVCameraStream*[1];
    m_paStreams[0] = new CVCameraStream(phr, this, L"Virtual Flash Cam");
}

HRESULT CVCamera::QueryInterface(REFIID riid, void **ppv)
{
    //Forward request for IAMStreamConfig & IKsPropertySet to the pin
    if(riid == _uuidof(IAMStreamConfig) || riid == _uuidof(IKsPropertySet))
        return m_paStreams[0]->QueryInterface(riid, ppv);
    else
        return CSource::QueryInterface(riid, ppv);
}
//temp



STDMETHODIMP CVCamera::GetPages(CAUUID *pPages)//somehow hwnd of hosting app should be passed here.
{
	 if (pPages == NULL) return E_POINTER;
        pPages->cElems = 1;
        pPages->pElems = (GUID*)CoTaskMemAlloc(sizeof(GUID));
        if (pPages->pElems == NULL) 
        {
            return E_OUTOFMEMORY;
        }
        pPages->pElems[0] = CLSID_SetupCVCameraProp;
        return S_OK;
		//an alternative is described here http://groups.google.com/group/microsoft.public.multimedia.directx.dshow.programming/browse_thread/thread/1fe03eb5d363275c

		//and here http://groups.google.com/group/microsoft.public.win32.programmer.directx.video/browse_thread/thread/2390b9b3538561ce - though it is not like the correct answer.
}

STDMETHODIMP CVCamera::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    if (riid == IID_ISpecifyPropertyPages)
    {
        return GetInterface(
           static_cast<ISpecifyPropertyPages*>(this),
           ppv);
    }
    else if (riid == IID_ICVCamera)
    {
        return GetInterface(static_cast<ICVCamera*>(this), ppv);
    }
    else
    {
        // Call the parent class.
        return CBaseFilter::NonDelegatingQueryInterface(riid, ppv);

        // NOTE: This example assumes that the filter inherits directly
        // from CBaseFilter. If your filter inherits from another class,
        // call the NonDelegatingQueryInterface method of that class.
		//SK CSource does not redefine this method.
    }
}

//////////////////////////////////////////////////////////////////////////
// CVCameraStream is the one and only output pin of CVCamera which handles 
// all the stuff.
//////////////////////////////////////////////////////////////////////////
CVCameraStream::CVCameraStream(HRESULT *phr, CVCamera *pParent, LPCWSTR pPinName) :
    CSourceStream(NAME("Virtual Flash Cam"),phr, pParent, pPinName), m_pParent(pParent)
{
	//m_pFilter =0;
	image = 0;
    // Set the default media type as 320x240x24@15
	int iPosition = 1;
	defaultiPos=iPosition;
	numCap=16; 
	GetMediaType(iPosition, &m_mt);

	
	
	
	DECLARE_PTR(VIDEOINFOHEADER, pvi, m_mt.pbFormat);
	//should be set here.
	m_width      = pvi->bmiHeader.biWidth;
    m_height     = pvi->bmiHeader.biHeight;

	

		
}

CVCameraStream::~CVCameraStream()
{
	if(image)
    {
      delete image;
      image= NULL;
      DeleteObject(m_hBmp);
    }
} 

HRESULT CVCameraStream::QueryInterface(REFIID riid, void **ppv)
{   
    // Standard OLE stuff
    if(riid == _uuidof(IAMStreamConfig))
        *ppv = (IAMStreamConfig*)this;
    else if(riid == _uuidof(IKsPropertySet))
        *ppv = (IKsPropertySet*)this;
    else
        return CSourceStream::QueryInterface(riid, ppv);

    AddRef();
    return S_OK;
}


//////////////////////////////////////////////////////////////////////////
//  This is the routine where we create the data being output by the Virtual
//  Camera device.
//////////////////////////////////////////////////////////////////////////

HRESULT CVCameraStream::FillBuffer(IMediaSample *pms)
{
    REFERENCE_TIME rtNow;
    
    REFERENCE_TIME avgFrameTime = ((VIDEOINFOHEADER*)m_mt.pbFormat)->AvgTimePerFrame;

    rtNow = m_rtLastTime;
    m_rtLastTime += avgFrameTime;
    pms->SetTime(&rtNow, &m_rtLastTime);
    pms->SetSyncPoint(TRUE);

	
    BYTE *pData;
    long lDataLen;
    pms->GetPointer(&pData);
    lDataLen = pms->GetSize();
	
	if(!image)
	{
	  LoadAndResize();
	}
	if(image)
	{
	  memcpy(pData,image->GetBits(),lDataLen);
	}
	else
	{
	  memset(pData,0,lDataLen);
	}
    return NOERROR;
} // FillBuffer


//
// Notify
// Ignore quality management messages sent from the downstream filter
STDMETHODIMP CVCameraStream::Notify(IBaseFilter * pSender, Quality q)
{
    return E_NOTIMPL;
} // Notify

//////////////////////////////////////////////////////////////////////////
// This is called when the output format has been negotiated
//////////////////////////////////////////////////////////////////////////
HRESULT CVCameraStream::SetMediaType(const CMediaType *pmt)
{
    DECLARE_PTR(VIDEOINFOHEADER, pvi, pmt->Format());
    HRESULT hr = CSourceStream::SetMediaType(pmt);
    return hr;
}

void computeWidthAndHeight(int iPos,VIDEOINFOHEADER* _pvi)
{
    

	switch(iPos) 	
	{
    case 0:
	
	    _pvi->bmiHeader.biWidth      =  640;
	    _pvi->bmiHeader.biHeight     =  480;
	 
		break;


	case 1:
	
	    _pvi->bmiHeader.biWidth      =  120;
	    _pvi->bmiHeader.biHeight     =  60;//first goes lowest height with biggest width for this height.
	 
		break;


   case 2:
	
	    _pvi->bmiHeader.biWidth      = 200;
	    _pvi->bmiHeader.biHeight     = 96;
	
	    break;

	case 3:
	    _pvi->bmiHeader.biWidth      = 160;
	    _pvi->bmiHeader.biHeight     = 120;

	
		break;
	case 4:
	
	    _pvi->bmiHeader.biWidth      = 140;
	    _pvi->bmiHeader.biHeight     = 120;

    case 5:

	    _pvi->bmiHeader.biWidth      = 120;
	    _pvi->bmiHeader.biHeight     = 120;
	
	    break;
	
	 

	case 6:
	    _pvi->bmiHeader.biWidth      = 300;
	    _pvi->bmiHeader.biHeight     = 200;
	 break;

	 case 7:
	    _pvi->bmiHeader.biWidth      = 80;//lower width should go later with the same height.
	    _pvi->bmiHeader.biHeight     = 200;
	 break;

	


	
	case 8:

	    _pvi->bmiHeader.biWidth      = 320;
	    _pvi->bmiHeader.biHeight     = 240;
		break;


 	case 9:
	
	    _pvi->bmiHeader.biWidth      = 440;
	    _pvi->bmiHeader.biHeight     = 300;
	  
		break;

	case 10:

	    _pvi->bmiHeader.biWidth      = 420;
	    _pvi->bmiHeader.biHeight     = 300;
	
		break;

	case 11:
	
	    _pvi->bmiHeader.biWidth      = 400;
	    _pvi->bmiHeader.biHeight     = 300;
	
		break;

  
	case 12:
	    _pvi->bmiHeader.biWidth      = 480;
	    _pvi->bmiHeader.biHeight     = 360;
		break;

	case 13:
	
	    _pvi->bmiHeader.biWidth      = 400;
	    _pvi->bmiHeader.biHeight     = 360;

		break;


	case 14:
	
	    _pvi->bmiHeader.biWidth      = 400;
	    _pvi->bmiHeader.biHeight     = 380;
	 
		break;


	case 15:
	    _pvi->bmiHeader.biWidth      = 560;
	    _pvi->bmiHeader.biHeight     = 420;
		break;

    case 16:
	    _pvi->bmiHeader.biWidth      = 580;
	    _pvi->bmiHeader.biHeight     = 440;
		break;

	
	default:
	//should not be here.
	    _pvi->bmiHeader.biWidth      = 640;
	    _pvi->bmiHeader.biHeight     = 480;
	 
		break;


	}


}

// See Directshow help topic for IAMStreamConfig for details on this method
HRESULT CVCameraStream::GetMediaType(int iPosition, CMediaType *pmt)
{
    if(iPosition < 0) return E_INVALIDARG;
    if(iPosition > numCap-1) //NB here no need to return anything beyond number of numCap  will suffice too in this case
		return VFW_S_NO_MORE_ITEMS;

    if(iPosition == 0) //320x240
    {
		DECLARE_PTR(VIDEOINFOHEADER, pvi, m_mt.pbFormat);
        *pmt = m_mt;
        return S_OK;
    }

    DECLARE_PTR(VIDEOINFOHEADER, pvi, pmt->AllocFormatBuffer(sizeof(VIDEOINFOHEADER)));
    ZeroMemory(pvi, sizeof(VIDEOINFOHEADER));

    pvi->bmiHeader.biCompression = BI_RGB;
    pvi->bmiHeader.biBitCount    = 24;
    pvi->bmiHeader.biSize       = sizeof(BITMAPINFOHEADER);
    computeWidthAndHeight(iPosition,pvi);
    pvi->bmiHeader.biPlanes     = 1;
    pvi->bmiHeader.biSizeImage  = GetBitmapSize(&pvi->bmiHeader);
    pvi->bmiHeader.biClrImportant = 0;

    pvi->AvgTimePerFrame = 1000000;

    SetRectEmpty(&(pvi->rcSource)); // we want the whole image area rendered.
    SetRectEmpty(&(pvi->rcTarget)); // no particular destination rectangle

    pmt->SetType(&MEDIATYPE_Video);
    pmt->SetFormatType(&FORMAT_VideoInfo);
    pmt->SetTemporalCompression(FALSE);

    // Work out the GUID for the subtype from the header info.
    const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader);
    pmt->SetSubtype(&SubTypeGUID);
    pmt->SetSampleSize(pvi->bmiHeader.biSizeImage);

	
    
    return NOERROR;

} // GetMediaType

// This method is called to see if a given output format is supported
HRESULT CVCameraStream::CheckMediaType(const CMediaType *pMediaType)
{
    VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)(pMediaType->Format());
    if(*pMediaType != m_mt)
        return E_INVALIDARG;

    return S_OK;
} // CheckMediaType

// This method is called after the pins are connected to allocate buffers to stream data
HRESULT CVCameraStream::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties)
{
    CAutoLock cAutoLock(m_pFilter->pStateLock());
    HRESULT hr = NOERROR;

    VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *) m_mt.Format();
    pProperties->cBuffers = 1;
    pProperties->cbBuffer = pvi->bmiHeader.biSizeImage;

    ALLOCATOR_PROPERTIES Actual;
    hr = pAlloc->SetProperties(pProperties,&Actual);

    if(FAILED(hr)) return hr;
    if(Actual.cbBuffer < pProperties->cbBuffer) return E_FAIL;

    return NOERROR;
} // DecideBufferSize

// Called when graph is run
HRESULT CVCameraStream::OnThreadCreate()
{
    m_rtLastTime = 0;
    return NOERROR;
} // OnThreadCreate

bool  CVCameraStream::LoadAndResize()
{
	//are set m_width,m_height
if(image)
{
	delete image;
	image = 0;
}
 {
	WCHAR    FileName[MAX_PATH];

	HANDLE hProcess = GetCurrentProcess();
	GetModuleBaseNameW(hProcess, NULL, FileName, MAX_PATH);
	GetModuleFileNameW(g_hInst,FileName,MAX_PATH);
	//find left "\"
	
	std::wstring temp=FileName;

	int pos = temp.find_last_of(__W("\\"));
	int length = temp.length();
	length = length-( length - pos )+1;
	temp = temp.substr(0,length);
	//add ini name
	CIniReader m_IniReader;
	std::wstring m_strINIFile = temp;
	m_strINIFile = m_strINIFile + __W("VirtCam.ini");
    m_IniReader.setINIFileName (m_strINIFile);
	std::wstring m_strSectionName =__W("Picture to show");
	std::wstring m_strKeyValue =__W("");
	std::wstring m_strKeyName =__W("path");

	bool bExists = m_IniReader.sectionExists(m_strSectionName);
	if(bExists)
	{
		m_strKeyValue = m_IniReader.getKeyValue(m_strKeyName,m_strSectionName);
	}
	if(m_strKeyValue != __W(""))
	{
	  temp=temp+m_strKeyValue;
	  if(LoadBackImage(temp.c_str())>=0)
	  {
	  }
	  else
	  {
		  image =0;
	  }
	 
	 
	
 }
}
 return true;
}

//////////////////////////////////////////////////////////////////////////
//  IAMStreamConfig
//////////////////////////////////////////////////////////////////////////

HRESULT STDMETHODCALLTYPE CVCameraStream::SetFormat(AM_MEDIA_TYPE *pmt)
{
	if(!pmt) return E_POINTER;
	
	DECLARE_PTR(VIDEOINFOHEADER, pvi,pmt->pbFormat);
	
	VIDEOINFOHEADER testvi;
  //check if format is among listed
	bool found = false;

	//for(int i = 0;i<numCap;++i)
	//{
	//	computeWidthAndHeight(i,&testvi);
	//	if(testvi.bmiHeader.biWidth == pvi->bmiHeader.biWidth &&
	//		testvi.bmiHeader.biHeight == pvi->bmiHeader.biHeight)
	//	{
	//		found = true;
	//		break;// go 
	//	}

	//}
	//if(!found)
	//	return E_INVALIDARG;

	

    // If the pin is already using this format, there's nothing to do.
    if (IsConnected() && (m_mt == *pmt))
    {
        return S_OK;
    }

 //   // See if this media type is acceptable.
	HRESULT hr;
  

    // If we're connected to a downstream filter, we have to make
    // sure that the downstream filter accepts this media type.
    if (IsConnected()) 
    {
        hr = GetConnected()->QueryAccept(pmt);
        if (hr != S_OK)
        {
            return VFW_E_INVALIDMEDIATYPE;
        }
    }

	//OK go
	m_width  = pvi->bmiHeader.biWidth;
	m_height = pvi->bmiHeader.biHeight;

	

	LoadAndResize();
	
	{
	    m_mt = *pmt;
	    IPin* pin; 
	    ConnectedTo(&pin);
	    if(pin)
	    {
	        IFilterGraph *pGraph = m_pParent->GetGraph();
	        pGraph->Reconnect(this);
	    }
    return S_OK;
	}
	
}

HRESULT STDMETHODCALLTYPE CVCameraStream::GetFormat(AM_MEDIA_TYPE **ppmt)
{
    *ppmt = CreateMediaType(&m_mt);
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CVCameraStream::GetNumberOfCapabilities(int *piCount, int *piSize)
{
    *piCount = numCap;//NB here the number of returned sizes could be set.
    *piSize = sizeof(VIDEO_STREAM_CONFIG_CAPS);
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CVCameraStream::GetStreamCaps(int iIndex, AM_MEDIA_TYPE **pmt, BYTE *pSCC)
{
    *pmt = CreateMediaType(&m_mt);
    DECLARE_PTR(VIDEOINFOHEADER, pvi, (*pmt)->pbFormat);

	pvi->bmiHeader.biCompression = BI_RGB;
	pvi->bmiHeader.biBitCount    = 24;
	pvi->bmiHeader.biSize       = sizeof(BITMAPINFOHEADER);
	computeWidthAndHeight(iIndex,pvi);
    pvi->bmiHeader.biPlanes     = 1;
    pvi->bmiHeader.biSizeImage  = GetBitmapSize(&pvi->bmiHeader);
    pvi->bmiHeader.biClrImportant = 0;

   
    SetRectEmpty(&(pvi->rcSource)); // we want the whole image area rendered.
    SetRectEmpty(&(pvi->rcTarget)); // no particular destination rectangle

    (*pmt)->majortype = MEDIATYPE_Video;
    (*pmt)->subtype = MEDIASUBTYPE_RGB24;
    (*pmt)->formattype = FORMAT_VideoInfo;
    (*pmt)->bTemporalCompression = FALSE;
	(*pmt)->bFixedSizeSamples= TRUE;//not compressed FALSE;
    (*pmt)->lSampleSize = pvi->bmiHeader.biSizeImage;
    (*pmt)->cbFormat = sizeof(VIDEOINFOHEADER);
    
    DECLARE_PTR(VIDEO_STREAM_CONFIG_CAPS, pvscc, pSCC);
    
    pvscc->guid = FORMAT_VideoInfo;
    pvscc->VideoStandard = AnalogVideo_None;
    pvscc->InputSize.cx = 640;
    pvscc->InputSize.cy = 480;
    pvscc->MinCroppingSize.cx = 80;
    pvscc->MinCroppingSize.cy = 60;
    pvscc->MaxCroppingSize.cx = 640;
    pvscc->MaxCroppingSize.cy = 480;
    pvscc->CropGranularityX = 80;
    pvscc->CropGranularityY = 60;
    pvscc->CropAlignX = 0;
    pvscc->CropAlignY = 0;

    pvscc->MinOutputSize.cx = 80;
    pvscc->MinOutputSize.cy = 60;
    pvscc->MaxOutputSize.cx = 640;
    pvscc->MaxOutputSize.cy = 480;
    pvscc->OutputGranularityX = 0;
    pvscc->OutputGranularityY = 0;
    pvscc->StretchTapsX = 0;
    pvscc->StretchTapsY = 0;
    pvscc->ShrinkTapsX = 0;
    pvscc->ShrinkTapsY = 0;
    pvscc->MinFrameInterval = 200000;   //50 fps
    pvscc->MaxFrameInterval = 50000000; // 0.2 fps
    pvscc->MinBitsPerSecond = (80 * 60 * 3 * 8) / 5;
    pvscc->MaxBitsPerSecond = 640 * 480 * 3 * 8 * 50;

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
// IKsPropertySet
//////////////////////////////////////////////////////////////////////////


HRESULT CVCameraStream::Set(REFGUID guidPropSet, DWORD dwID, void *pInstanceData, 
                        DWORD cbInstanceData, void *pPropData, DWORD cbPropData)
{// Set: Cannot set any properties.
    return E_NOTIMPL;
}

// Get: Return the pin category (our only property). 
HRESULT CVCameraStream::Get(
    REFGUID guidPropSet,   // Which property set.
    DWORD dwPropID,        // Which property in that set.
    void *pInstanceData,   // Instance data (ignore).
    DWORD cbInstanceData,  // Size of the instance data (ignore).
    void *pPropData,       // Buffer to receive the property data.
    DWORD cbPropData,      // Size of the buffer.
    DWORD *pcbReturned     // Return the size of the property.
)
{
    if (guidPropSet != AMPROPSETID_Pin)             return E_PROP_SET_UNSUPPORTED;
    if (dwPropID != AMPROPERTY_PIN_CATEGORY)        return E_PROP_ID_UNSUPPORTED;
    if (pPropData == NULL && pcbReturned == NULL)   return E_POINTER;
    
    if (pcbReturned) *pcbReturned = sizeof(GUID);
    if (pPropData == NULL)          return S_OK; // Caller just wants to know the size. 
    if (cbPropData < sizeof(GUID))  return E_UNEXPECTED;// The buffer is too small.
        
    *(GUID *)pPropData = PIN_CATEGORY_CAPTURE;
    return S_OK;
}

// QuerySupported: Query whether the pin supports the specified property.
HRESULT CVCameraStream::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD *pTypeSupport)
{
    if (guidPropSet != AMPROPSETID_Pin) return E_PROP_SET_UNSUPPORTED;
    if (dwPropID != AMPROPERTY_PIN_CATEGORY) return E_PROP_ID_UNSUPPORTED;
    // We support getting this property, but not setting it.
    if (pTypeSupport) *pTypeSupport = KSPROPERTY_SUPPORT_GET; 
    return S_OK;
} 

std::wstring CVCameraStream::FindExtension(const wstring& name)
{
	int len = name.length(); 
	int i; 
	for (i = len-1; i >= 0; i--){
		if (name[i] == L'.'){
			return name.substr(i+1);
		}
	}
	return wstring(__W(""));
}

void MakeUpper(std::wstring &str)
{
std::transform(str.begin(),str.end(),str.begin(),toupper);
}

void MakeLower(std::wstring &str)
{
std::transform(str.begin(),str.end(),str.begin(),tolower);
}


std::string convertUTF16toUTF8(const wchar_t* source, unsigned sourceLength)
{

    if (sourceLength == 0)
    {
        return std::string();
    }

    int destLen = WideCharToMultiByte(CP_UTF8, 0, source, sourceLength, 0, 0, 0, 0);
    if (destLen <= 0)
    {
        //warn  "Cannot convert UTF-16 string to UTF-8." ;
        return std::string();
    }

    std::string sDest(destLen, '\0');
    destLen = WideCharToMultiByte(CP_UTF8, 0, source, sourceLength, &sDest[0], destLen, 0, 0);

    if (destLen <= 0)
    {
        //warn "Cannot convert UTF-16 string to UTF-8." << std::endl;
        return std::string();
    }

    return sDest;

}

std::string convertUTF16toUTF8(const std::wstring& s){return convertUTF16toUTF8(s.c_str(), s.length());}

/******************************Public*Routine******************************\
* LoadImage
*
* adds new image to the window
\**************************************************************************/
HRESULT CVCameraStream::LoadBackImage(const wchar_t *wcPath)
{
  

     HRESULT hr = -1;
    if( !wcPath )
    {
        return E_POINTER;
    }
   
    try
    {
     

	  CxImage *pSource = NULL;
      wstring ext(FindExtension(wcPath));
	  MakeLower(ext);
	  if (ext == __W("")) return -1;
     
	  std::string extz = convertUTF16toUTF8(ext);
	  DWORD type = CxImage::GetTypeIdFromName(extz.c_str());
      std::string pathz = convertUTF16toUTF8(wcPath);
      pSource = new CxImage(pathz.c_str(),type);
      RGBQUAD rgbquad;
      rgbquad.rgbBlue=0x00;
      rgbquad.rgbGreen=0x00;
      rgbquad.rgbRed=0x00;

	  if(pSource->IsValid())
      {
        //check bpp
        int bpp = pSource->GetBpp();   

		//set correct bpp
		if(bpp <24)
			pSource->IncreaseBpp(24);

		//get size

		int sourcewidth = pSource->GetWidth();
		int sourceheight= pSource->GetHeight();

		//now make decisions 

		if(m_width < sourcewidth || m_height < sourceheight)
		{
			pSource->Thumbnail (m_width,m_height,rgbquad);
		}
		else if(m_width == sourcewidth && m_height == sourceheight)
		{//do nothing
		}
		else
		{
			pSource->Expand(m_width,m_height,rgbquad);
		}

        image = pSource;
        
        hr = S_OK;
      
      }
      else
       
      {
       
        delete pSource;
        pSource = NULL;
        image = NULL;
    
         return  -1;
      }





  
    }
    catch( HRESULT hr1 )
    {
    
        hr = hr1;
    }
    return hr;
}